#include "phy.h"
#include "mii.h"

#define PHY_LOG_DOMAIN      "phy"
#define phy_debug(...)      qelog_debug(PHY_LOG_DOMAIN, __VA_ARGS__)
#define phy_info(...)       qelog_info(PHY_LOG_DOMAIN, __VA_ARGS__)
#define phy_warning(...)    qelog_warning(PHY_LOG_DOMAIN, __VA_ARGS__)
#define phy_error(...)      qelog_error(PHY_LOG_DOMAIN, __VA_ARGS__)



QE_LIST_INIT(phy_drivers);



void phy_driver_register(phy_driver *drv)
{
    qe_list_append(&drv->list, &phy_drivers);
}

static qe_ret genphy_setup_forced(phy_dev *dev)
{
    int ctl = BMCR_ANRESTART;

    dev->pause = 0;
    dev->asym_pause = 0;

    if (dev->speed == SPEED_1000)
        ctl |= BMCR_SPEED1000;
	else if (dev->speed == SPEED_100)
		ctl |= BMCR_SPEED100;

	if (dev->duplex == DUPLEX_FULL)
		ctl |= BMCR_FULLDPLX;

    return phy_write(dev, MII_BMCR, ctl);
}

static qe_ret genphy_config_advert(phy_dev *dev)
{
    qe_ret ret;
    int adv, oldadv, bmsr;
    qe_u32 advertise;
    qe_bool changed = 0;

    /* Only allow advertising what this PHY supports */
    dev->advertising &= dev->supported;
    advertise = dev->advertising;

    /* Setup standard advertisement */
    adv = phy_read(dev, MII_ADVERTISE);
    oldadv = adv;

	if (adv < 0)
		return qe_err_read;

    adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4 | ADVERTISE_PAUSE_CAP |
		 ADVERTISE_PAUSE_ASYM);
	if (advertise & ADVERTISED_10baseT_Half)
		adv |= ADVERTISE_10HALF;
	if (advertise & ADVERTISED_10baseT_Full)
		adv |= ADVERTISE_10FULL;
	if (advertise & ADVERTISED_100baseT_Half)
		adv |= ADVERTISE_100HALF;
	if (advertise & ADVERTISED_100baseT_Full)
		adv |= ADVERTISE_100FULL;
	if (advertise & ADVERTISED_Pause)
		adv |= ADVERTISE_PAUSE_CAP;
	if (advertise & ADVERTISED_Asym_Pause)
		adv |= ADVERTISE_PAUSE_ASYM;
	if (advertise & ADVERTISED_1000baseX_Half)
		adv |= ADVERTISE_1000XHALF;
	if (advertise & ADVERTISED_1000baseX_Full)
		adv |= ADVERTISE_1000XFULL;

    if (adv != oldadv) {
        ret = phy_write(dev, MII_ADVERTISE, adv);
        if (ret != qe_ok) {
            return ret;
        }
        changed = 1;
    }

    bmsr = phy_read(dev, MII_BMSR);
	if (bmsr < 0)
		return qe_err_read;

	/* Per 802.3-2008, Section 22.2.4.2.16 Extended status all
	 * 1000Mbits/sec capable PHYs shall have the BMSR_ESTATEN bit set to a
	 * logical 1.
	 */
	if (!(bmsr & BMSR_ESTATEN))
		return changed;
}

qe_ret genphy_config_aneg(phy_dev *dev)
{
    qe_ret ret;

    if (dev->autoneg != AUTONEG_ENABLE)
		return genphy_setup_forced(dev);

    ret = genphy_config_advert(dev);
    if (ret != qe_ok)
        return ret;


}

qe_ret genphy_config(phy_dev *dev)
{
    int val;
    qe_u32 features;

	features = (SUPPORTED_TP | SUPPORTED_MII
			| SUPPORTED_AUI | SUPPORTED_FIBRE |
			SUPPORTED_BNC);

	features;

    /* Do we support autonegotiation? */
    val = phy_read(dev, MII_BMSR);
    if (val < 0)
        return qe_err_read;

    if (val & BMSR_ANEGCAPABLE)
        features |= SUPPORTED_Autoneg;
    
	if (val & BMSR_100FULL)
		features |= SUPPORTED_100baseT_Full;
	if (val & BMSR_100HALF)
		features |= SUPPORTED_100baseT_Half;
	if (val & BMSR_10FULL)
		features |= SUPPORTED_10baseT_Full;
	if (val & BMSR_10HALF)
		features |= SUPPORTED_10baseT_Half;

    if (val & BMSR_ESTATEN) {
        val = phy_read(dev, MII_ESTATUS);
        if (val < 0)
			return qe_err_read;
		if (val & ESTATUS_1000_TFULL)
			features |= SUPPORTED_1000baseT_Full;
		if (val & ESTATUS_1000_THALF)
			features |= SUPPORTED_1000baseT_Half;
		if (val & ESTATUS_1000_XFULL)
			features |= SUPPORTED_1000baseX_Full;
		if (val & ESTATUS_1000_XHALF)
			features |= SUPPORTED_1000baseX_Half;
    }

	dev->supported &= features;
	dev->advertising &= features;

    return genphy_config_aneg(dev);
}

qe_ret phy_get_id(mii_dev *bus, int addr, qe_u16 *phy_id)
{
    int phy_reg;

	/*
	 * Grab the bits from PHYIR1, and put them
	 * in the upper half
	 */
    phy_reg = bus->read(bus, addr, MII_PHYSID1);
    if (phy_reg < 0) {
        return qe_err_read;
    }

    *phy_id = phy_reg & 0xffff;

    return qe_ok;
}

static phy_driver *phy_get_driver(qe_u16 phy_id)
{
    phy_driver *drv;

//    qe_list_foreach_entry(drv, &phy_drivers, list) {
//        if (drv->phyid == phy_id) {
//            return drv;
//        }
//    }

    return QE_NULL;
}

static qe_ret phy_probe(phy_dev *phy)
{
    if (phy->drv->probe) {
        return phy->drv->probe(phy);
    }

    return qe_ok;
}

phy_dev *phy_connect(mii_dev *bus, int addr, phy_interface_mode interface)
{
    qe_u16 phy_id = 0xffff;

    phy_dev *dev;
    phy_driver *drv;

    phy_get_id(bus, addr, &phy_id);
    phy_info("phy id 0x%x", phy_id);

    drv = phy_get_driver(phy_id);
    if (!drv) {
        phy_warning("no phy driver matched for phy id 0x%x", phy_id);
        return QE_NULL;
    }

    dev = qe_malloc(sizeof(phy_dev));
    if (!dev) {
        phy_error("alloc mem for phy dev failed");
        return QE_NULL;
    }
    qe_memset(dev, 0, sizeof(*dev));

    dev->bus = bus;
    dev->addr = addr;
    dev->phy_id = phy_id;
    dev->drv = drv;
    dev->link = 0;
    dev->interface = interface;

    phy_probe(dev);

    return dev;
}

qe_ret phy_config(phy_dev *phy)
{
	if (phy->drv->config) {
		return phy->drv->config(phy);
	}
	return qe_ok;
}
